Repository navigation
Deprecate passing bare coroutine and generator functions to param.rx - #1227
Merged
Merged
Conversation
Passing a coroutine, generator or async generator function as a reference without param.bind or param.depends now emits a ParamFutureWarning pointing to param.bind(fn). In a future release such functions will be kept as plain values, like any other function.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1227 +/- ##
==========================================
+ Coverage 89.42% 89.48% +0.06%
==========================================
Files 9 9
Lines 6588 6666 +78
==========================================
+ Hits 5891 5965 +74
- Misses 697 701 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
hoxbro
reviewed
Oct 26, 2026
hoxbro
left a comment
Member
There was a problem hiding this comment.
There is some other places which accepts bare async function with no warnings. Is that expected?
These was flagged by AI: .rx.pipe(f, afunc), rx([1]) + [afunc], rx.where operands and .rx.value = afunc
Move the deprecation warning into resolve_value so rx pipe arguments, operands, where branches and rx.value assignments also warn, unwrap partials in the warning message, and make the non-ref parameter test wait for any scheduled task. Assisted-by: Claude Code:claude-opus-5-5
Parameters with allow_refs=True keep resolving bare coroutine and generator functions, since streaming into a parameter is a documented feature. Only rx warns, at construction, for rx(fn), operation arguments, where branches, collect and rx.value assignments. Assisted-by: Claude Code:claude-opus-5-5
Member
Author
|
Thanks for the review. The scope has changed since, so here's where things landed:
|
Calling them to resolve a reference always failed with a TypeError, so rx now keeps them as values like any other function, without a warning. Parameters keep calling them, so a forgotten bind still fails loudly. Assisted-by: Claude Code:claude-opus-5-5
- Read signatures without evaluating annotations on Python 3.14+, so annotations naming TYPE_CHECKING-only imports no longer raise NameError - Give operation inputs their own warning, since they are called but never resolved, instead of suggesting param.bind - Warn when a bare async function is assigned as an override - Keep functions requiring arguments as values when assigned to the .rx.value of a root built from an async function - Do not warn for functions inside sets, which are never resolved - Consolidate the coroutine/generator function check into _is_async_fn - Update the awaiting docstrings and drop unused test parameters Assisted-by: Claude Code:claude-opus-5-5
Calling an rx expression whose value is a coroutine, generator or async generator function, or an async method of the wrapped object, now awaits or iterates the result like .rx.pipe does instead of producing an unawaited coroutine. This prepares rx(afunc)() for when bare async functions are kept as values. Whether the call is async is decided from the callee when the call is created. Assisted-by: Claude Code:claude-opus-5-5
hoxbro
approved these changes
Oct 30, 2026
Co-authored-by: Simon Høxbro Hansen <simon.hansen@me.com>
hoxbro
approved these changes
Oct 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
param.rxtreats functions inconsistently. A plain function passed torx(fn)is wrapped as a value you can call or pipe, but a coroutine, generator or async generator function is called and its result resolved. The same split shows up in operation arguments:rx(x).rx.pipe(f, afunc),rx([1]) + [afunc],rx.where(afunc, ...)andexpr.rx.value = afuncall call a bare async function while passing a sync one through untouched. For an API whose job is to wrap values this is surprising, and resolving outside a running event loop leads to errors like "coroutine was never awaited".This PR starts moving
rxto a single rule, where every function is a value unless it is explicitly declared a reference withparam.bindorparam.depends:ParamFutureWarningtelling users to wrap them withparam.bind(fn), which keeps today's behavior. In a future releaserxwill keep them as values.TypeError, so nothing that works today changes. Arguments bound by apartialcount as supplied, and so do defaults, so a function with only defaulted arguments still falls under the deprecation.Both rules apply wherever
rxresolves a value:rx(fn), operation arguments (including ones nested in lists, tuples, dicts and slices),wherebranches,collect,rx.valueassignments and.rx.overrides. The warning fires once, when the expression or operation is built, not on every re-evaluation.The warning's advice depends on the entry point. Only
rx(fn)actually resolves the function, so only there does the message suggestparam.bind(fn)(orparam.bind(fn, ...)for a partial). Everywhere else the function is called but its coroutine or async generator is passed on without ever being awaited or iterated, so the message says so and that the function itself will be passed on in future. Functions inside asetare never resolved and don't warn.Calling an async function held by
rxOnce bare async functions are kept as values, the natural way to call one reactively is
rx(afunc)(x). Until now that produced an unawaited coroutine, because a call operation is stored as'__call__'and never qualified as async. Calling an expression whose value is a coroutine, generator or async generator function, or an async method of the wrapped object, now awaits or iterates the result exactly like.rx.pipe(afunc, x), including.rx.awaitingand re-resolving when inputs change:rx(afunc)(x)previously raised aTypeError, so nothing relied on it.rx(obj).amethod(x)previously returned the coroutine object. Whether a call is async is decided from the callee when the call is created, so swapping the held function between a sync and an async one viarx.valuekeeps the original mode, and a callee coming out of an upstream async node that hasn't resolved yet isn't detected.Parameters are unchanged
Parameters with
allow_refs=Truekeep resolving bare coroutine and generator functions, without a warning, and still raise when such a function requires arguments, so a forgottenbindfails loudly. Resolving references is the point ofallow_refs, and streaming a generator into a parameter is a documented feature that Panel relies on, e.g.pn.pane.Markdown(object=gen)and therefs=argument. An earlier version of this PR that also warned on parameters would have affected three Panel docs pages and Panel's ownrefs=code path, and broke one Panel unit test. Nothing in Panel's docs, tests or internals passes a bare async function torx, so this version needs no Panel changes.One related fix does touch parameters: those that are implicitly
allow_refs=Falseno longer speculatively resolve a bare coroutine or generator function just to check whether to warn about a reference. That speculative resolve started a coroutine nobody awaited, and it could eventually overwrite the parameter's value with the result.Tests and docs
The existing
rxtests and therxexample in the Generators user guide now useparam.bind(...), and the.rx.awaitingsection of the Reactive Expressions guide shows calling an async function and an async method. New tests intests/testrefs.pycover, for coroutine, async generator, generator and partial functions:rxentry point, its message per entry point, and that it fires only oncerx.valueon a root built from an async function, while defaults-only functions are still resolvedTYPE_CHECKING-only imports on Python 3.14+) still worksNew tests in
tests/testreactive.pycover calling async, async generator and generator functions and async methods throughrx.The full Param test suite passes locally on Python 3.11 and 3.14. Against this branch, Panel's
test_reactive,paneandlayoutsuites pass, and of the common Panel patterns onlypn.rx(fn)warns.AI Disclosure
Tool & Model: Claude Code + Claude Opus 5.5
Usage: Investigated the reference resolution code paths, implemented the warning, the handling of functions requiring arguments and awaiting async calls, addressed review findings, updated tests and docs, and measured the impact on Panel's docs and test suite.
Checklist